package simpleftp.server;

import java.io.*;
import java.net.*;
import java.util.*;
import java.lang.Void;
import java.util.concurrent.Callable;

import simpleftp.client.control.Command;

/**
 * ServeurFTP is a simple package that implements a Java FTP server. With
 * ServeurFTP, you can connect to an FTP client and download multiple files. 
 */
public class FTPServer implements Callable<Void> {
	
	private static boolean DEBUG = false;
	private String response, user;
	private int validations;
	private File f;
//	private StringBuilder sb; // Old stuff, not needed anymore.
	private int portnum1, portnum2;
	private Properties p;
	private ServerSocket dl;
	private Socket dl2;
	private FileInputStream fis;
	private Socket socket = null;
	private BufferedReader reader = null;
	private BufferedWriter writer = null;
	private ObjectOutputStream out; // Added
	
	public FTPServer(Socket s) {
		socket = s;
		validations = 0;
		try {
			p = new Properties();
			fis = new FileInputStream("users.properties");
			p.load(fis);
			try {
				reader = new BufferedReader(new InputStreamReader(
						socket.getInputStream()));
				writer = new BufferedWriter(new OutputStreamWriter(
						socket.getOutputStream()));
				out    = new ObjectOutputStream(
						socket.getOutputStream()); // Added. To send the serialized FileTreeModel, or File.
				
				sendLine("220 ");
				validations++;
				f = new File(System.getProperty("user.dir"));
				System.out.println("FTP Server: new client from " + socket.getInetAddress().getHostAddress());
			} catch (IOException e) {
				try {
					sendLine("error");
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				e.printStackTrace();
			}
		}
		catch (IOException ioex)  {
			System.out.println("Error: Properties File didn't load");
			try {
				sendLine("error ");
			} catch (IOException e) {
				e.printStackTrace();
			}
			ioex.printStackTrace();
		}
	}

	
	public Void call() {
		try {
			response = readLine();
			if (response.startsWith("USER")) {
				if (p.containsKey(response.substring(5))) {
					user = response.substring(5);
					System.out.println("Server knows user " + response);
					sendLine("331 ");
					validations++;
				} else {
					System.out.println("Server does no know user " + response);
					sendLine("error ");
				}
			}
			response = readLine();
			if (response.startsWith("PASS")) {
				if (p.get((String) user).equals(response.substring(5))) {
					System.out.println("Server validates pass " + response);
					sendLine("230 ");
					validations++;
				} else {
					sendLine("error ");
				}
			}

		} 
		catch (IOException e) {
			try {
				sendLine("error ");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}

		while (validations == 3) {
			try {
				response = readLine();
				switch (Command.valueOf(response.split(" ")[0])) {
					case PWD:
						sendLine("257 " + System.getProperty("user.dir"));
						break;
	
					case LIST:
						// Bye bye old stuff
						/*sb = new StringBuilder("200 ");
						String[] fileNames = f.list();
						for (String fileName : fileNames)
							sb = sb.append(fileName + "|");
						sendLine(sb.toString());*/

						sendObject(f); // Sends a TreeModel would be better but since we have to populate the Tree 
									   // in the client-side, I'll do it that way for the moment.
						break;
	
					case CWD:
						// Updated. Removed old stuff and new method so we can go in parent dir "..".
						//System.setProperty("user.dir", response.substring(4));
						//System.out.println("CWD new current directory: " + System.getProperty("user.dir").toString());
						//f = new File(System.getProperty("user.dir"));
						System.setProperty("user.dir", 
								System.getProperty("user.dir")
								+ System.getProperty("file.separator")
								+ response.substring(4));
						f = new File(System.getProperty("user.dir"));
						sendLine("250 ");
						break;
	
					case PASV:
						pasv();
						break;
	
					case STOR:
						stor();
						break;
						
					case RETR: // Added. To download a file from FTP.
						download();
						break;
	
					case QUIT:
						socket.close();
						System.out.println("QUIT: Goodbye");
						validations = 0;
						break;
	
					default:
						System.out.println("Unknown Command.");
						sendLine("error");
						break;
				}
			}
			catch (IOException e) {
				try {
					sendLine("error");
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				e.printStackTrace();
			}
		}
		Void Void = null;
		return Void;
	}
	
	/**
	 * Sends a serializable Object to the socket's OutputStream, assuming the Object implements the Serializable interface.
	 * @param o the Object to send.
	 */
	public void sendObject(Object o) {
		try {
			out.writeObject(o);
			out.flush();
			if (DEBUG)
				System.out.println("> " + o.toString());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Send on BufferedWriter. Most used to send back results of commands.
	 * @param line
	 * @throws IOException
	 */
	private void sendLine(String line) throws IOException {
		if (socket == null) 
			throw new IOException("FTPServer is not connected.");
		
		try {
			writer.write(line + "\r\n");
			writer.flush();
			if (DEBUG)
				System.out.println("> " + line);
		} catch (IOException e) {
			socket = null;
			throw e;
		}
	}

	/**
	 * Reads from BufferedReader.
	 * @return
	 * @throws IOException
	 */
	private String readLine() throws IOException {
		String line = reader.readLine();
		if (DEBUG)
			System.out.println("< " + line);
		return line;
	}

	/**
	 * Receives a file as an InputStrem via a Socket and store it on a new File created in the current working directory.
	 */
	private void stor() {
		try {
			dl2 = dl.accept();
			sendLine("150 ");
			OutputStream os = new FileOutputStream(new File(
					          System.getProperty("user.dir")
							+ System.getProperty("file.separator")
							+ response.substring(5)));
			InputStream is = dl2.getInputStream();
				byte[] buffer = new byte[4096];
				int bytesRead = 0;
				while ((bytesRead = is.read(buffer)) != -1)
					os.write(buffer, 0, bytesRead);
			
			os.flush();
			os.close();
			is.close();
			sendLine("226 ");
			dl.close();
			dl2.close();
			System.out.println("STOR " + response.split(" ")[1] + ": File received");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Sends a file as an OutputStream. Basically a mirror of stor().
	 */
	private void download() {
		try {	
			dl2 = dl.accept();
			sendLine("150 ");
			FileInputStream fileInput = new FileInputStream(new File(
					  System.getProperty("user.dir")
					+ System.getProperty("file.separator")
					+ response.substring(5)));
			BufferedOutputStream output = new BufferedOutputStream(dl2.getOutputStream());
				byte[] buffer = new byte[4096];
				int bytesRead = 0;
				while ((bytesRead = fileInput.read(buffer)) != -1)
					output.write(buffer, 0, bytesRead);
			
			output.flush();
			output.close();
			fileInput.close();
			sendLine("226 ");
			dl.close();
			dl2.close();
			System.out.println("DOWNLOAD " + response.substring(9) + ": File sent");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void pasv() {
		try {
			dl = new ServerSocket(0);
			portnum1 = dl.getLocalPort() / 256;
			portnum2 = dl.getLocalPort() % 256;
			sendLine("227 ("
					+ InetAddress.getLocalHost().getHostAddress()
							.replace('.', ',') + "," + portnum1 + ","
					+ portnum2 + ")");
			System.out.println("PASV success");
		} catch (IOException e) {
			try {
				sendLine("error ");
				System.out.println("pasv error");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
	}
	
	public static void setDebug(boolean b) {
		DEBUG = b;
	}
}
